type Coord = (usize, usize);

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Cell {
    OpenTile,
    SolidWall,
}

#[derive(Debug)]
enum Command {
    Left,
    Right,
    Move(usize),
}

#[derive(Debug, Clone, Copy)]
enum Direction {
    Left,
    Up,
    Right,
    Down,
}

impl Direction {
    fn turn_left(&mut self) {
        match self {
            Self::Left => *self = Self::Down,
            Self::Up => *self = Self::Left,
            Self::Right => *self = Self::Up,
            Self::Down => *self = Self::Right,
        }
    }

    fn turn_right(&mut self) {
        match self {
            Self::Left => *self = Self::Up,
            Self::Up => *self = Self::Right,
            Self::Right => *self = Self::Down,
            Self::Down => *self = Self::Left,
        }
    }

    fn point(&self) -> usize {
        match self {
            Self::Left => 2,
            Self::Up => 3,
            Self::Right => 0,
            Self::Down => 1,
        } 
    }
}

struct Puzzle {
    board: Vec<Vec<Option<Cell>>>,
    commands: Vec<Command>,
}

fn parse_input() -> Puzzle {
    let input = include_str!("../input.txt");
    let mut iter = input.split("\r\n\r\n");
    let board_part = iter.next().unwrap();
    let commands_part = iter.next().unwrap();

    let mut board = board_part
        .lines()
        .map(|line| -> Vec<Option<Cell>> {
            line
                .chars()
                .map(|c| -> Option<Cell> {
                    match c {
                        ' ' => None,
                        '.' => Some(Cell::OpenTile),
                        '#' => Some(Cell::SolidWall),
                        _ => panic!("Bad input"),
                    }
                }) 
                .collect()
        })
        .collect::<Vec<_>>();

    let max_width = board.iter().map(|l| l.len()).max().unwrap();
    for line in &mut board {
        if max_width > line.len() {
            let times = max_width - line.len();
            for _ in 0..times {
                line.push(None);
            }
        }
    }

    fn parse_command(input: &str) -> (Command, &str) {
        match &input[..1] {            
            "L" => (Command::Left, &input[1..]),
            "R" => (Command::Right, &input[1..]),
            _ => {
                let digit_count = input
                    .chars()
                    .take_while(|c| c.is_digit(10))
                    .count();

                let move_distance = input[..digit_count].parse().unwrap();
                (Command::Move(move_distance), &input[digit_count..])
            }
        }
    }

    let mut commands = Vec::new();
    let mut input = commands_part;
    loop {
        let (command, left_str) = parse_command(input);
        commands.push(command);
        if left_str.is_empty() {
            break;
        } else {
            input = left_str;
        }
    }

    Puzzle { board, commands }
}

#[derive(Debug)]
struct State {
    cur_coord: Coord,
    cur_dir: Direction,
}

impl State {
    fn new(puzzle: &Puzzle) -> Self {
        // First line, not empty
        let coord_x = puzzle.board[0].iter().take_while(|&&c| c.is_none()).count();
        State { 
            cur_coord: (coord_x as usize, 0), 
            cur_dir: Direction::Right, 
        }
    }

    fn next_coord(&self, puzzle: &Puzzle) -> Coord {
        let (x, y) = self.cur_coord;
        match self.cur_dir {
            Direction::Left => {
                if x == 0 || puzzle.board[y][x - 1].is_none() {
                    let empty_count = puzzle.board[y].iter().rev().take_while(|c| c.is_none()).count();
                    (puzzle.board[y].len() - 1 - empty_count, y)
                } else {
                    (x - 1, y)
                }
            }
            Direction::Up => {
                if y == 0 || puzzle.board[y - 1][x].is_none() {
                    let mut next_y = puzzle.board.len() - 1;
                    while puzzle.board[next_y][x].is_none() {
                        next_y -= 1;
                    }
                    (x, next_y)
                } else {
                    (x, y - 1)
                }
            }
            Direction::Right => {
                if x == puzzle.board[y].len() - 1 || puzzle.board[y][x + 1].is_none() {
                    let next_x = puzzle.board[y].iter().take_while(|&&c| c.is_none()).count();
                    (next_x, y)
                } else {
                    (x + 1, y)
                }
            }
            Direction::Down => {
                if y == puzzle.board.len() - 1 || puzzle.board[y + 1][x].is_none() {
                    let mut next_y = 0;
                    while puzzle.board[next_y][x].is_none() {
                        next_y += 1;
                    }
                    (x, next_y)
                } else {
                    (x, y + 1)
                }
            }
        }
    }

    fn execute(&mut self, command: &Command, puzzle: &Puzzle) {
        match command {
            Command::Left => self.cur_dir.turn_left(),
            Command::Right => self.cur_dir.turn_right(),
            Command::Move(distance) => {
                for _ in 0..*distance {
                    let next_coord = self.next_coord(puzzle);
                    let (x, y) = next_coord;
                    let next_cell = puzzle.board[y][x].unwrap();
                    if next_cell == Cell::SolidWall {
                        // Stop
                        break;
                    } else {
                        self.cur_coord = next_coord;
                    }
                }
            }
        }
    }

    fn password(&self) -> usize {
        let (x, y) = self.cur_coord;
        1000 * (y + 1) + 4 * (x + 1) + self.cur_dir.point()
    }
}

fn part1() {
    let puzzle = parse_input();
    let mut state = State::new(&puzzle);
    for command in &puzzle.commands {
        state.execute(command, &puzzle);
    }
    println!("Part 1: {}", state.password());
}

fn main() {
    part1();
}
